在昨天的文章裡,我們最後有遇到一個問題,當我們傳入的 props 是一個物件時,元件在重新渲染的時候,這些值都會被重新建立,所以被視為不同的 props,導致重新渲染,這時候我們可以使用 React 提供的 useMemo
跟 useCallback
來處理。
import { useState, memo } from "react";
function App() {
const [count, setCount] = useState(0);
// 每次 App 重新渲染的時候,都會重新建立這一個物件
const archiveOptions = {
show: false,
title: "Post archive"
};
return (
<section>
<h2>{count}</h2>
<button onClick={() => setCount((count) => count + 1)}>++</button>
// archiveOptions 被重新建立,導致 Archive 重新渲染
<Archive archiveOptions={archiveOptions} />
</section>
);
}
const Archive = memo(function Archive({ show, title }) {
console.log(show);
return <h1>{title}</h1>;
});
useMemo
是 React 提供的一個 Hook,主要用於記憶住我們的值,讓它們不會在每次渲染的時候都重新建立,使用方法是傳入一個 callback function,並且最後返回一個值。
import { useState, useMemo, memo } from "react";
function App() {
...
const archiveOptions = useMemo(() => {
return {
show: false,
title: 'Post archive',
};
});
return (
...
);
}
useMemo
和 useEffect
一樣有一個依賴陣列,主要用途是當其中的依賴項發生變化時,React 會幫我們重新建立這一個值,確保可以跟狀態保持同步。
import { useState, memo } from "react";
function App() {
const [posts, setPosts] = useState([]);
const [count, setCount] = useState(0);
const archiveOptions = useMemo(() => {
return {
show: false,
title: `Post archive in addition to ${posts.length} main posts`,
};
}, [posts.length]);
return (
<section>
<h2>{count}</h2>
<button onClick={() => setCount((count) => count + 1)}>++</button>
<Archive archiveOptions={archiveOptions} />
</section>
);
}
const Archive = memo(function Archive({ show, title }) {
console.log(show);
return <h1>{title}</h1>;
});
useCallback
在用法上和 useMemo
十分類似,差別在於 useCallback
要記憶住的是函式,因為每個函式在重新渲染的時候也會被重新建立,導致子元件重新渲染的問題。
import { useState, memo } from "react";
function App() {
...
// 每次 App 重新渲染的時候,都會重新建立這一個函式
function handleAddPost(post) {
setPosts((posts) => [post, ...posts]);
});
return (
<section>
<h2>{count}</h2>
<button onClick={() => setCount((count) => count + 1)}>++</button>
// handleAddPost 被重新建立,導致 Archive 重新渲染
<Archive
archiveOptions={archiveOptions}
onAddPost={handleAddPost}
/>
</section>
);
}
使用方法是將函式放入 useCallback
裡面,再重新賦予一個變數,useCallback
也會也有一個依賴陣列,去告訴 React 何時需要重新建立函式。
import { useState, memo } from "react";
function App() {
...
const handleAddPost = useCallback(function handleAddPost(post) {
setPosts((posts) => [post, ...posts]);
}, []);
return (
<section>
<h2>{count}</h2>
<button onClick={() => setCount((count) => count + 1)}>++</button>
<Archive
archiveOptions={archiveOptions}
onAddPost={handleAddPost}
/>
</section>
);
}
這邊有一個有趣的點,當我們傳入的 props 是一個 setter 函式,會發現 Archive
不會重新渲染,這是因為 React 確保了 useState
的 setter 函式始終具有穩定的身份,這意味著它們不會在渲染時發生變化(可以把 setter 函式想像成已經自動記憶化)。
import { useState, memo } from "react";
function App() {
const [posts, setPosts] = useState([]);
const [count, setCount] = useState(0);
return (
<section>
<h2>{count}</h2>
<button onClick={() => setCount((count) => count + 1)}>++</button>
<Archive setPosts={setPosts} />
</section>
);
}
當元件重新渲染的時候,元件內部所有的值都會重新建立(包括物件、函式),因此,假設我們傳入 memo
元件的 props 是物件或函式,還是會觸發它的重新渲染,因為每次的 props 都被視為一個新的值,所以我們可以使用 useMemo
記憶值、useCallback
記憶函式,並傳入一個依賴陣列,告訴 React 當依賴項發生變化的時候,幫我們重新建立新的值。